package com.android.server.alarm;

import android.os.Environment;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;
import android.util.Slog;
import android.util.TypedXmlSerializer;

import com.android.server.job.controllers.JobStatus;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class HardwareCollector {
    private String TAG = HardwareCollector.class.getSimpleName();
    private HashMap<Integer,ArrayList<String>> hardwareTable;
    private String tableName = "hardware.xml";
    private String batteryName = "battery.log";
    private ArrayList<String> stopItem;
    AtomicFile mHardwareFile;
    AtomicFile mBatteryFile;

    final Object mLock = new Object();

    public HardwareCollector(){
        // Initial hardware collector
        hardwareTable = new HashMap();
        stopItem = new ArrayList<>();

        File systemDir = new File(Environment.getDataDirectory(),"system");
        File alarmDir = new File(systemDir,"alarm");
        mBatteryFile = new AtomicFile(new File(alarmDir,this.batteryName));
        mHardwareFile = new AtomicFile(new File(alarmDir,this.tableName));

        String[] item = {"vers", "uid", "apk", "wua", "st", "wl", "awl", "sy", "jb", "jbc", "jbd", "wr", "pwi"};
        for (String str : item){
            stopItem.add(str);
        }

        // Get the table from local if start up the phone
        loadFromLocal();
    }

    // Update the table of hardware and uid
    public void updateHardwareTable() throws IOException {
        String line;
        BufferedReader in;
        String words[];
        String splitcsv = ",";

        // dump batterystats
        dumpBatteryStats();

        // handle batterystats
        synchronized (mLock){
            in = new BufferedReader(new InputStreamReader(mBatteryFile.openRead()));

            while((line = in.readLine())!= null) {
                int uid;
                words = line.split(splitcsv);
                if (!isNumber(words[1]))
                    continue;
                else
                    uid = Integer.valueOf(words[1]).intValue();
                if (Integer.valueOf(words[1]).intValue() < 1000)
                    continue;
                if (stopItem.contains(words[3]))
                    continue;
                if (hardwareTable.containsKey(uid)) {
                    if (!hardwareTable.get(uid).contains(words[3]))
                        hardwareTable.get(uid).add(words[3]);
                } else {
                    ArrayList<String> val = new ArrayList<String>();
                    val.add(words[3]);
                    hardwareTable.put(uid, val);
                }
            }
            Log.d(TAG,"**** update successed ****");
        }

        // TODO:Save the table to local
        writeToLocal();
    }

    private boolean isNumber(String str){
        for (int i = str.length();--i>=0;){
            if (!Character.isDigit(str.charAt(i))){
                return false;
            }
        }
        return true;
    }

    // dump batterystats to local
    private void dumpBatteryStats(){
        FileOutputStream out1 = null;
        FileDescriptor fd = new FileDescriptor();
        synchronized (mLock) {
            try {
                out1 = mBatteryFile.startWrite();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fd = out1.getFD();
            } catch (IOException e) {
                e.printStackTrace();
            }
            String[] args = new String[1];
            args[0] = "--checkin";
            android.os.Debug.dumpService("batterystats", fd, args);
            mBatteryFile.finishWrite(out1);
        }
    }

    // Get the hardware similarity
    public double getHardwareSimilarity(ArrayList<String> lst1, ArrayList<String> lst2){
        // union set
        ArrayList<String> lst3 = lst1;
        lst3.addAll(lst2);
        int len1 = lst3.size();
        int len2 = lst1.size() + lst2.size() - len1;
        if(len1 == 0)
            return 0;
        return len2 / len1;
    }

    // Get hardware from table
    public ArrayList<String> getUidHardware(int uid){
        if (hardwareTable.containsKey(uid))
            return hardwareTable.get(uid);
        else
            return new ArrayList<String>();
    }

    // Write hardware map to local
    public void writeToLocal(){
        Iterator<Map.Entry<Integer,ArrayList<String>>> entries = hardwareTable.entrySet().iterator();
        try (FileOutputStream fos = mHardwareFile.startWrite()){
            TypedXmlSerializer out = Xml.resolveSerializer(fos);
            out.startDocument(null, true);
            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);

            out.startTag(null, "hardware_uid_table-info");
            while (entries.hasNext()){
                Map.Entry<Integer,ArrayList<String>> entry = entries.next();
                out.startTag(null,"hardwareSet");
                out.attribute(null,"uid",Integer.toString(entry.getKey()));
                for (String str : entry.getValue()){
                    out.startTag(null,"hardwares");
                    out.attribute(null,"hardware",str);
                    out.endTag(null,"hardwares");
                }
                out.endTag(null,"hardwareSet");
            }
            out.endTag(null, "hardware_uid_table-info");
            out.endDocument();
            mHardwareFile.finishWrite(fos);
        } catch (IOException e) {
            Slog.v(TAG,"Error writing out hardware_uid table",e);
        }
    }

    // Load the table from local file when device is frist start up.
    private void loadFromLocal(){
        String tagName;
        int uid = 0;
        ArrayList<String> hardwares;

        try(FileInputStream fis = mHardwareFile.openRead()){
            synchronized (mLock){
                XmlPullParser parser = Xml.resolvePullParser(fis);
                int eventType = parser.getEventType();
                while (eventType != XmlPullParser.START_TAG &&
                        eventType != XmlPullParser.END_DOCUMENT) {
                    eventType = parser.next();
                }
                if (eventType == XmlPullParser.END_DOCUMENT) {
                    return;
                }

                eventType = parser.next();
                do {
                    // Read each <hardwareSet/>
                    if (eventType == XmlPullParser.START_TAG) {
                        tagName = parser.getName();
                        hardwares = new ArrayList<>();
                        // Start reading table.
                        if("hardwareSet".equals(tagName)){
                            uid = Integer.parseInt(parser.getAttributeValue(null,"uid"));
                            eventType = parser.next();
                            do{
                                if(eventType == XmlPullParser.START_TAG){
                                    tagName = parser.getName();
                                    if("hardwares".equals(tagName)){
                                        hardwares.add(parser.getAttributeValue(null,"hardware"));
                                    }
                                }
                            }while("hardwareSet".equals(parser.getName()));
                            hardwareTable.put(uid,hardwares);
                        }
                    }
                    eventType = parser.next();
                } while (eventType != XmlPullParser.END_DOCUMENT);
            }
        } catch (XmlPullParserException | FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
